// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: daniel

#include "coin2/exchange/crypto/order_v2/parser.h"

#include <utility>

#include "coin/proto/coin_query.pb.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::crypto::order_v2 {

namespace {

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;

inline int64_t GetProcOrderId(const JsonValue& data) {
  int64_t proc_order_id = 0;
  if (data.HasMember("client_oid")) {
    std::string proc_str = data["client_oid"].GetString();
    try {
      proc_order_id = stoll(proc_str);
    } catch (std::exception& e) {
      LOG(ERROR) << e.what() << proc_str;
    }
  }
  return proc_order_id;
}

}  // namespace

bool CryptoParser::IsCryptoErrMessage(const JsonValue& data) const {
  /*
  {
    "method": "...",
    "code": 0,
    "result": {
      ...
    }
  }
  or
  {
    "code":"100001",
    "msg":"SYS_ERROR"
  }
  */
  std::string code = "1";
  if (data.HasMember("code")) {
    code = data["code"].GetString();
  }
  return code != "0";
}

auto CryptoParser::GetOrderSide(std::string_view side) const -> OrderSide {
  if (side == "BUY") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "SELL") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

auto CryptoParser::GetOrderType(std::string_view type) const -> OrderType {
  if (type == "LIMIT") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "MARKET") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void CryptoParser::SetOrderStatus(const JsonValue& data, ProductOrderElement* product_order_element)
    const {
  // TODO(daniel): ACTIVE, CANCELED, FILLED, REJECTED or EXPIRED
  std::string_view status = data["status"].GetString();
  if (status == "ACTIVE") {
    product_order_element->set_is_live(true);
  } else if (status == "REJECTED" || status == "EXPIRED") {
    product_order_element->set_is_live(false);
  } else if (status == "FILLED") {
    product_order_element->set_fully_filled(true);
  } else if (status == "CANCELED") {
    product_order_element->set_cancel_confirmed(true);
  } else {
    LOG(ERROR) << "[ParseRestOrderInfo] Unknown order status! " << status;
  }
}

void CryptoParser::ParseSubmitResponse() {
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }
  const auto* order_context = msg_->oc;

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[CryptoSubmitOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[CryptoSubmitOrder] Parsing Error." << msg_->DebugString();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }
  if (!document.IsObject()) {
    LOG(ERROR) << "[CryptoSubmitOrder] failed. invalid format. " << JsonToString(document);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  if (IsCryptoErrMessage(document)) {
    LOG(ERROR) << "[CryptoSubmitOrder] failed. id: [" << order_context->proc_order_id() << "], "
               << http_context->res;
    auto&& error_code = document["code"].GetString();
    auto&& error_msg = document["message"].GetString();
    resp->set_exchange_error_code(error_code);
    resp->set_exchange_error_msg(error_msg);
    // TODO(daniel): group error
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }

  // as the response is simply a confirmation of the request.
  // so just ignore the response with no error
  resp->Clear();
  result_.Clear();
  result_.set_type(MsgType::IGNORABLE);
}

void CryptoParser::ParseCancelResponse() {
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }
  const auto* order_context = msg_->oc;

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    LOG(ERROR) << "[CryptoCancelOrder] AsioHttpContextStatus Error";
    return;
  }

  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[CryptoCancelOrder] Parsing Error." << msg_->DebugString();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }
  if (!document.IsObject()) {
    LOG(ERROR) << "[CryptoCancelOrder] failed. invalid format. " << JsonToString(document);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  if (IsCryptoErrMessage(document)) {
    LOG(ERROR) << "[CryptoCancelOrder] failed. id: [" << order_context->proc_order_id() << "], "
               << http_context->res;
    std::string error_code = document["code"].GetString();
    std::string error_msg = document["message"].GetString();
    resp->set_exchange_error_code(error_code);
    resp->set_exchange_error_msg(error_msg);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  // as the response is simply a confirmation of the request.
  // so just ignore the response with no error
  // resp->Clear();
  // result_.Clear();
  // result_.set_type(MsgType::IGNORABLE);
  resp->set_success(true);
  resp->set_cancel_confirmed(false);
}

void CryptoParser::ParseRestOrderInfo(
    const JsonValue& data,
    ProductOrderElement* product_order_element) {
  /*
  {
    "status": "ACTIVE",
    "side": "BUY",
    "price": 1,
    "quantity": 1,
    "order_id": "366543374673423753",
    "client_oid": "my_order_0002",
    "create_time": 1588760643829,
    "update_time": 1588760644292,
    "type": "LIMIT",
    "instrument_name": "ETH_CRO",
    "cumulative_quantity": 0,
    "cumulative_value": 0,
    "avg_price": 0,
    "fee_currency": "CRO",
    "time_in_force": "GOOD_TILL_CANCEL"
  }
  */

  product_order_element->set_market_type(MarketType::Spot);
  product_order_element->set_exchange_type(ExchangeType::Crypto);
  product_order_element->set_api_version("v2");
  product_order_element->set_exchange_order_id(data["order_id"].GetString());
  product_order_element->set_symbol(data["instrument_name"].GetString());
  product_order_element->set_price(stod(data["price"].GetString()));
  product_order_element->set_qty(stod(data["quantity"].GetString()));
  product_order_element->set_proc_order_id(GetProcOrderId(data));
  product_order_element->set_side(GetOrderSide(data["side"].GetString()));
  product_order_element->set_order_type(GetOrderType(data["type"].GetString()));
  product_order_element->set_timestamp(msg_->timestamp);
  SetOrderStatus(data, product_order_element);
}

void CryptoParser::ParseRestFillOrders() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestFillOrders] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsCryptoErrMessage(document)) {
    LOG(ERROR) << "[ParseRestFillOrders] failed. " << JsonToString(document);
    return;
  }

  auto pfb = result_.mutable_fill_list();
  pfb->Clear();

  if (ParseRestFillInfo(document, pfb)) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

bool CryptoParser::ParseRestFillInfo(
    const JsonValue& data,
    ProductFillBundle* product_fill_bundle) {
  /*
  {
    "side": "SELL",
    "fee": 0.00028984,
    "trade_id": "2310870943932464323",
    "instrument_name": "XRP_USDT",
    "create_time": 1646706120867,
    "traded_price": 0.72459,
    "traded_quantity": 0.1,
    "fee_currency": "USDT",
    "order_id": "2310869826969751204",
    "client_oid": "1646705987380345780",
    "liquidity_indicator": "MAKER"
  }
  */
  if (!data.HasMember("result")) {
    LOG(ERROR) << "[ParseRestFillInfo] failed. missing field [result]" << JsonToString(data);
    return false;
  }
  auto&& obj = data["result"].GetObject();
  if (!obj.HasMember("trade_list") || !obj["trade_list"].IsArray()) {
    LOG(ERROR) << "[ParseRestFillInfo] failed. missing field [trade_list]" << JsonToString(data);
    return false;
  }
  for (const auto& info : obj["trade_list"].GetArray()) {
    std::string native_symbol = info["instrument_name"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(native_symbol);
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(info["order_id"].GetString());
    fill->set_proc_order_id(GetProcOrderId(info));
    fill->set_price(stod(info["traded_price"].GetString()));
    fill->set_qty(stod(info["traded_quantity"].GetString()));
    fill->set_fill_id(info["trade_id"].GetString());

    auto native_currency = info["fee_currency"].GetString();
    if (symcache_->has_currency_native(native_currency)) {
      auto* currency = symcache_->GetCurrencyByNative(native_currency);
      fill->set_fee_currency(currency->currency());
      fill->set_fee(stod(info["fee"].GetString()));
    }

    using FillType = coin::proto::FillType;
    std::string fill_type = info["liquidity_indicator"].GetString();
    if (fill_type == "MAKER") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (fill_type == "TAKER") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
  }
  return true;
}

void CryptoParser::ParseRestOpenOrders() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestOpenOrders] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto pob = result_.mutable_order_list();
  pob->Clear();

  if (document.IsObject() && IsCryptoErrMessage(document)) {
    LOG(ERROR) << "[ParseRestOpenOrders] failed. " << JsonToString(document);
    return;
  }
  if (!document.HasMember("result")) {
    LOG(ERROR) << "[ParseRestOpenOrders] failed. missing field [result]." << JsonToString(document);
    return;
  }
  auto&& obj = document["result"].GetObject();
  if (!obj.HasMember("order_list") || !obj["order_list"].IsArray()) {
    LOG(ERROR) << "[ParseRestOpenOrders] failed. missing field [order_list]."
               << JsonToString(document);
    return;
  }
  bool product_order_bundle_found = false;
  for (const auto& info : obj["order_list"].GetArray()) {
    if (!symcache_->has_product_native(info["instrument_name"].GetString())) {
      continue;
    }
    auto order = pob->add_each_order();
    ParseRestOrderInfo(info, order);
    product_order_bundle_found = true;
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void CryptoParser::ParseRestDoneOrders() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestDoneOrders] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto pob = result_.mutable_order_list();
  pob->Clear();

  if (document.IsObject() && IsCryptoErrMessage(document)) {
    LOG(ERROR) << "[ParseRestDoneOrders] failed. " << JsonToString(document);
    return;
  }
  if (!document.HasMember("result")) {
    LOG(ERROR) << "[ParseRestDoneOrders] failed. missing field [result]." << JsonToString(document);
    return;
  }
  auto&& obj = document["result"].GetObject();
  if (!obj.HasMember("order_list") || !obj["order_list"].IsArray()) {
    LOG(ERROR) << "[ParseRestDoneOrders] failed. missing field [order_list]."
               << JsonToString(document);
    return;
  }
  bool product_order_bundle_found = false;
  for (const auto& info : obj["order_list"].GetArray()) {
    if (!symcache_->has_product_native(info["instrument_name"].GetString())) {
      continue;
    }
    auto order = pob->add_each_order();
    ParseRestOrderInfo(info, order);
    product_order_bundle_found = true;
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

auto CryptoParser::ParseBalance(const JsonValue& document) -> AccountBalance {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(document, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Crypto");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool CryptoParser::ParseRawBalance(const JsonValue& document, RawBalanceMap* raw_balance_map) {
  /*
  {
      "id": 11,
      "method": "private/get-account-summary",
      "code": 0,
      "result": {
          "accounts": [
              {
                  "balance": 99999999.905000000000000000,
                  "available": 99999996.905000000000000000,
                  "order": 3.000000000000000000,
                  "stake": 0,
                  "currency": "CRO"
              }
          ]
      }
  }
  */
  if (document.IsObject() && IsCryptoErrMessage(document)) {
    LOG(ERROR) << "[ParseBalance] failed. " << JsonToString(document);
    return false;
  }
  if (!document.HasMember("result")) {
    LOG(ERROR) << "[ParseBalance] failed. " << JsonToString(document);
    return false;
  }
  auto&& data = document["result"].GetObject();
  if (!data.HasMember("accounts") || !data["accounts"].IsArray()) {
    LOG(ERROR) << "[ParseBalance] failed. " << JsonToString(document);
    return false;
  }
  for (const auto& balance : data["accounts"].GetArray()) {
    auto&& currency_str = GetNativeCurrency(balance["currency"].GetString());
    CurrencyBalance currency_balance;
    currency_balance.set_currency_native(currency_str);
    auto total = stod(balance["balance"].GetString());
    auto available = stod(balance["available"].GetString());
    currency_balance.set_total(total);
    currency_balance.set_available(available);
    currency_balance.set_hold(total - available);
    raw_balance_map->emplace(currency_balance.currency_native(), currency_balance);
  }

  return true;
}

void CryptoParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

void CryptoParser::ParseBalanceFromWs(const JsonValue& data) {
  /*
  [
    {
      "balance": "499.91912312",
      "available": "499.91912312",
      "order": "0.0",
      "stake": "0",
      "currency": "USDT"
    }
  ]
   */
  if (!is_account_balance_initialized_) {
    return;
  }

  result_.set_type(MsgType::ACCOUNT_BALANCE);
  auto* acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Crypto");
  acc_balance->set_market_type("Spot");

  bool is_account_balance_found = false;
  for (const auto& info : data.GetArray()) {
    std::string native_currency = GetNativeCurrency(info["currency"].GetString());
    if (!symcache_->has_currency_native(native_currency)) {
      continue;
    }
    auto* balance = acc_balance->add_each_balance();
    balance->set_total(stod(info["balance"].GetString()));
    balance->set_available(stod(info["available"].GetString()));
    balance->set_hold(balance->total() - balance->available());
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    is_account_balance_found = true;
  }
  if (!is_account_balance_found) {
    result_.Clear();
    result_.set_type(MsgType::IGNORABLE);
  }
}

void CryptoParser::ParseOrderFromWs(const JsonValue& data) {
  /*
  [
    {
      "status": "FILLED",
      "side": "SELL",
      "price": "0.72083",
      "quantity": "0.1",
      "order_id": "2311495079935493248",
      "client_oid": "1646724321853713587",
      "create_time": "1646724721569",
      "update_time": "1646724723262",
      "type": "LIMIT",
      "instrument_name": "XRP_USDT",
      "avg_price": "0.72083",
      "cumulative_quantity": "0.1",
      "cumulative_value": "0.072083",
      "fee_currency": "USDT",
      "exec_inst": "POST_ONLY",
      "time_in_force": "GOOD_TILL_CANCEL"
    }
  ]
   */
  result_.set_type(MsgType::ORDER_STATUS);
  auto* pob = result_.mutable_order_list();
  pob->Clear();

  bool product_order_bundle_found = false;
  for (const auto& info : data.GetArray()) {
    std::string native_symbol = info["instrument_name"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto* order = pob->add_each_order();
    order->set_market_type(MarketType::Spot);
    order->set_exchange_type(ExchangeType::Crypto);
    order->set_api_version("v2");
    order->set_exchange_order_id(info["order_id"].GetString());
    order->set_symbol(native_symbol);
    order->set_price(stod(info["price"].GetString()));
    order->set_qty(stod(info["quantity"].GetString()));
    order->set_proc_order_id(GetProcOrderId(info));
    order->set_side(GetOrderSide(info["side"].GetString()));
    order->set_order_type(GetOrderType(info["type"].GetString()));
    order->set_timestamp(msg_->timestamp);
    SetOrderStatus(info, order);
    product_order_bundle_found = true;
  }
  if (!product_order_bundle_found) {
    result_.Clear();
    result_.set_type(MsgType::IGNORABLE);
  }
}

void CryptoParser::ParseTradeFromWs(const JsonValue& data) {
  /*
  [
    {
      "side": "SELL",
      "fee": "2.8833E-4",
      "trade_id": "2311495136737207811",
      "instrument_name": "XRP_USDT",
      "create_time": "1646724723262",
      "traded_price": "0.72083",
      "traded_quantity": "0.1",
      "fee_currency": "USDT",
      "order_id": "2311495079935493248",
      "client_oid": "1646724321853713587",
      "liquidity_indicator": "MAKER"
    }
  ]
   */
  result_.set_type(MsgType::ORDER_FILL);
  auto* pfb = result_.mutable_fill_list();
  pfb->Clear();

  bool product_fill_bundle_found = false;
  for (const auto& info : data.GetArray()) {
    std::string native_symbol = info["instrument_name"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto* fill = pfb->add_each_fill();
    fill->set_symbol(native_symbol);
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(info["order_id"].GetString());
    fill->set_proc_order_id(GetProcOrderId(info));
    fill->set_price(stod(info["traded_price"].GetString()));
    fill->set_qty(stod(info["traded_quantity"].GetString()));
    fill->set_fill_id(info["trade_id"].GetString());
    auto native_currency = info["fee_currency"].GetString();
    if (symcache_->has_currency_native(native_currency)) {
      auto* currency = symcache_->GetCurrencyByNative(native_currency);
      fill->set_fee_currency(currency->currency());
      fill->set_fee(stod(info["fee"].GetString()));
    }
    using FillType = coin::proto::FillType;
    std::string fill_type = info["liquidity_indicator"].GetString();
    if (fill_type == "MAKER") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (fill_type == "TAKER") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
    product_fill_bundle_found = true;
  }
  if (!product_fill_bundle_found) {
    result_.Clear();
    result_.set_type(MsgType::IGNORABLE);
  }
}

void CryptoParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  DLOG(INFO) << "OG ws msg: " << JsonToString(document);
  if (!document.IsObject() || IsCryptoErrMessage(document)) {
    LOG(ERROR) << "[ParseWsMessage] failed. " << JsonToString(document);
    return;
  }
  if (!document.HasMember("result")) {
    LOG(WARNING) << "[ParseWsMessage] ignored. " << JsonToString(document);
    return;
  }

  std::string channel = document["result"]["channel"].GetString();
  if ("user.balance" == channel) {
    ParseBalanceFromWs(document["result"]["data"]);
  } else if ("user.order" == channel) {
    ParseOrderFromWs(document["result"]["data"]);
  } else if ("user.trade" == channel) {
    ParseTradeFromWs(document["result"]["data"]);
  } else {
    LOG(ERROR) << "[ParseWsMessage] unknown channel. " << JsonToString(document);
  }
}

auto CryptoParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  // must REST_MESSAGE
  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::MULTI_CANCEL_RESPONSE:
      // pass
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseRestOpenOrders();
      break;
    case OrderSubTopicId::REST_DONE_ORDERS:
      ParseRestDoneOrders();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillOrders();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseRestAccountBalance();
      break;
    default:
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      break;
  }

  return result_;
}

}  // namespace coin2::exchange::crypto::order_v2
